/*
 * Copyright 2010 Alessio Stalla.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dynaspring;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractRefreshableApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;

/**
 * The primary DynaSpring application context implementation. Loads bean definitions from .lisp and .xml resources,
 * delegating to a LispBeanDefinitionReader.
 * @author Alessio Stalla
 *
 */
public class DynamicRefreshableApplicationContext extends AbstractRefreshableApplicationContext {

	private static final Log log = LogFactory.getLog(DynamicRefreshableApplicationContext.class);
	protected final Configuration configuration = new Configuration();
	protected final List<Resource> resources;
	
	public DynamicRefreshableApplicationContext() {
		this(new ArrayList<Resource>(0));
	}
	
	public DynamicRefreshableApplicationContext(ApplicationContext parent) {
		this(new ArrayList<Resource>(0), parent);
	}
	
	public DynamicRefreshableApplicationContext(URL[] resources, ApplicationContext parent) {
		this(Arrays.asList(resources), parent);
	}
	
	public DynamicRefreshableApplicationContext(URL... resources) {
		this(resources, null);
	}
	
	public DynamicRefreshableApplicationContext(String[] resources, ApplicationContext parent) {
		this(Arrays.asList(resources), parent);
	}
	
	public DynamicRefreshableApplicationContext(String... resources) {
		this(Arrays.asList(resources), null);
	}
	
	public DynamicRefreshableApplicationContext(List<?> resources) {
		this(resources, null);
	}
	
	public DynamicRefreshableApplicationContext(Object... resources) {
		this(Arrays.asList(resources), null);
	}
	
	public DynamicRefreshableApplicationContext(List<?> resources, ApplicationContext parent) {
		super(parent);
		this.resources = new ArrayList<Resource>(resources.size());
		ResourcePatternResolver patternResolver = ResourcePatternUtils.getResourcePatternResolver(this);
		for(Object o : resources) {
			if(o instanceof Resource) {
				this.resources.add((Resource) o);
			} else if(o instanceof URL) {
				this.resources.add(new UrlResource((URL) o));
			} else if(o instanceof String) {
				try {
					for(Resource r : patternResolver.getResources(o.toString())) {
						this.resources.add(r);
					}
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			} else if(o instanceof File) {
				this.resources.add(new FileSystemResource((File) o));
			}
		}
		PropertyPlaceholderConfigurer c = new PropertyPlaceholderConfigurer();
		c.setProperties(configuration);
		c.setIgnoreUnresolvablePlaceholders(true);
		addBeanFactoryPostProcessor(c);
		configuration.getVariables().put("spring::*application-context*", this);
	}
	
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory factory) throws IOException {
		log.info("Loading bean definitions");
		for(Resource res : resources) {
			loadBeanDefinitions(res, factory);
		}
	}
	
	public void loadBeanDefinitions(Resource res, DefaultListableBeanFactory factory) throws IOException {
		LispBeanDefinitionReader reader = new LispBeanDefinitionReader(factory, configuration);
		reader.loadBeanDefinitions(res);
	}
	
	/**
	 * Returns the list of resources to load bean definitions from.
	 */
	public List<Resource> getResources() {
		return resources;
	}
	
	/**
	 * Registers a variable with this context. Variables are available in the Lisp code executed during the activation of the context.
	 * @param key the name of the variable.
	 * @param value the value of the variable.
	 */
	public void put(String key, Object value) {
		getConfiguration().getVariables().put(key, value);
	}
	
	/**
	 * Returns the Properties representing this context's configuration. They should NOT be destructively modified by the caller.
	 * @return
	 */
	public Configuration getConfiguration() {
        return configuration;
    }
    
	public void setConfigurationProperties(Object... props) {
		configuration.reset();
		configuration.loadConfiguration(this, props);
	}
	
	@Override
	protected DefaultListableBeanFactory createBeanFactory() {
		return new LispBeanFactory(getInternalParentBeanFactory());
	}

	public BeanDefinition getBeanDefinition(String name) throws NoSuchBeanDefinitionException {
		return getBeanFactory().getBeanDefinition(name);
	}
	
}
